﻿
namespace WekaSharp

open WekaSharp
open weka

open Microsoft.FSharp.Collections

module Classify =
    open weka.classifiers

    let getClassifier (ctype:ClassifierType) (option:string)  =
        let classifier =
            match ctype with
                | J48 -> new classifiers.trees.J48() :> classifiers.Classifier
                | NaiveBayes -> new classifiers.bayes.NaiveBayes() :> classifiers.Classifier
                | KNN ->new classifiers.``lazy``.IBk() :> weka.classifiers.Classifier
                | LogReg -> new classifiers.functions.Logistic() :> classifiers.Classifier
                | NeuralNet -> new classifiers.functions.MultilayerPerceptron() :> classifiers.Classifier
                | SVM -> new classifiers.functions.LibSVM() :> classifiers.Classifier
                | LibLinear -> new classifiers.functions.LibLINEAR() :> classifiers.Classifier
                | LinReg -> new classifiers.functions.LinearRegression() :> classifiers.Classifier
                | AdaBoost -> new classifiers.meta.AdaBoostM1() :> classifiers.Classifier
                | Bagging -> new classifiers.meta.Bagging() :> classifiers.Classifier


        classifier.setOptions(core.Utils.splitOptions option)
        classifier

    let buildClassifier (ctype:ClassifierType) (option:string) (ds:Dataset) =
        Dataset.checkDatasetLabel ds
        let classifier = getClassifier ctype option
        classifier.buildClassifier(ds)
        classifier

    let getKNN (option:string) = getClassifier ClassifierType.KNN option :?> classifiers.``lazy``.IBk
    let getJ48 (option:string) = getClassifier ClassifierType.J48 option :?> classifiers.trees.J48
    let getSVM (option:string) = getClassifier ClassifierType.SVM option :?> classifiers.functions.LibSVM
    let getNaiveBayes (option:string) = getClassifier ClassifierType.NaiveBayes option :?> classifiers.bayes.NaiveBayes
    let getLogReg (option:string) = getClassifier ClassifierType.LogReg option :?> classifiers.functions.Logistic
    let getNeuralNet (option:string) = getClassifier ClassifierType.NeuralNet option :?> classifiers.functions.MultilayerPerceptron
    let getLinReg (option:string) = getClassifier ClassifierType.LinReg option :?> classifiers.functions.LinearRegression
    let getAdaBoost (option:string) = getClassifier ClassifierType.AdaBoost option :?> classifiers.meta.AdaBoostM1
    let getBagging (option:string) = getClassifier ClassifierType.Bagging option :?> classifiers.meta.Bagging

    let buildKNN (option:string) (ds:Dataset) = buildClassifier ClassifierType.KNN option ds 
    let buildJ48 (option:string) (ds:Dataset) = buildClassifier ClassifierType.J48 option ds 
    let buildSVM (option:string) (ds:Dataset) = buildClassifier ClassifierType.SVM option ds
    let buildNaiveBayes (option:string) (ds:Dataset) = buildClassifier ClassifierType.NaiveBayes option ds
    let buildLogReg (option:string) (ds:Dataset) = buildClassifier ClassifierType.LogReg option ds
    let buildNeuralNet (option:string) (ds:Dataset) = buildClassifier ClassifierType.NeuralNet option ds
    let buildLinReg (option:string) (ds:Dataset) = buildClassifier ClassifierType.LinReg option ds
    let buildAdaBoost (option:string) (ds:Dataset) = buildClassifier ClassifierType.AdaBoost option ds
    let buildBagging (option:string) (ds:Dataset) = buildClassifier ClassifierType.Bagging option ds

module Cluster = 
    open weka.clusterers

    let getClusterer (ctype:ClustererType) (option:string) = 
        let options = core.Utils.splitOptions option
        let clusterer = 
            match ctype with
                | KMeans -> 
                    let kmeans = new clusterers.SimpleKMeans() 
                    kmeans.setOptions(options)
                    kmeans :> clusterers.Clusterer 
                | EM ->  
                    let em = new clusterers.EM()
                    em.setOptions(options)
                    em :> clusterers.Clusterer
                | DBScan ->
                    let dbscan = new clusterers.DBScan()
                    dbscan.setOptions(options)
                    dbscan :> clusterers.Clusterer
        clusterer

    let buildClusterer (ctype:ClustererType) (option:string) (ds:Dataset) = 
        let cl = getClusterer ctype option
        cl.buildClusterer(ds)
        cl

    let getKmeans (option:string) = getClusterer ClustererType.KMeans option :?> clusterers.SimpleKMeans
    let getEM (option:string) = getClusterer ClustererType.EM option :?> clusterers.EM
    let getDBScan (option:string) = getClusterer ClustererType.DBScan option :?> clusterers.DBScan

    let buildKmeans (option:string) (ds:Dataset) = buildClusterer ClustererType.KMeans option ds
    let buildEM (option:string) (ds:Dataset) = buildClusterer ClustererType.EM option ds
    let buildDBScan (option:string) (ds:Dataset) = buildClusterer ClustererType.DBScan option ds
    
    
module Eval =
    let rec evalClassify (task:ClaEvalTask) =
        let rnd = new java.util.Random(System.DateTime.Now.Ticks)
        match task with
        | TrainTest (dsTrain, dsTest, ctype, para) ->
            Dataset.checkDatasetLabel dsTrain
            Dataset.checkDatasetLabel dsTest
            let eval = new classifiers.Evaluation(dsTrain)
            let cl = Classify.buildClassifier ctype para dsTrain
            eval.evaluateModel(cl, dsTest) |> ignore
            eval
        | CrossValidation(cv, ds, ctype, para) ->
            Dataset.checkDatasetLabel ds
            let eval = new classifiers.Evaluation(ds)
            let cl = Classify.getClassifier ctype para
            eval.crossValidateModel(cl, ds, cv, rnd, Array.empty)
            eval
        | RandomSplit(ratio, ds, ctype, para) ->
            Dataset.checkDatasetLabel ds
            let train, test = Dataset.randomSplit ratio ds
            evalClassify (TrainTest(train, test, ctype, para))

    let getAccuracy (eval:classifiers.Evaluation) = eval.pctCorrect()
    let getPrecison (eval:classifiers.Evaluation) (classIdx:int) = eval.precision(classIdx)
    let getRecall (eval:classifiers.Evaluation) (classIdx:int) = eval.recall(classIdx)
    let getF1 (eval:classifiers.Evaluation) (classIdx:int) = eval.fMeasure(classIdx)
    let getAUC (eval:classifiers.Evaluation) (classIdx:int) = eval.areaUnderROC(classIdx)
    let getClassifySummary (eval:classifiers.Evaluation) = eval.toSummaryString()

    let evalBulkClassify (tasks:ClaEvalTask seq) = 
        tasks
        |> Seq.map evalClassify
        |> Seq.toList

    let evalBulkClassifyParallel (task:ClaEvalTask seq) = 
        task
        |> PSeq.map evalClassify
        |> PSeq.toList

    let repeatEvalClassify (task:ClaEvalTask) (times:int) = 
        {1..times}
        |> Seq.map (fun _ -> evalClassify task)
        |> Seq.toList

    let repeatEvalClassifyParallel (task:ClaEvalTask) (times:int) = 
        {1..times}
        |> PSeq.map (fun _ -> evalClassify task)
        |> PSeq.toList

    let evalClustering (task: CluEvalTask) = 
        match task with 
        | DefaultCluster (ds, ctype, para) -> 
            if ds.classIndex() >=0 then
                failwith "evalClustering: DefaultCluster task does not support datasets with labels, you can remove/unset the class label index"
            let eval = new clusterers.ClusterEvaluation()
            let clu = Cluster.buildClusterer ctype para ds
            eval.setClusterer(clu)
            eval.evaluateClusterer(ds)
            eval
        | ClusterWithLabel (ds, ctype, para) ->
            // ref: weka manual s16.7, page 219
            Dataset.checkDatasetLabel ds
            let dsNoLabel = Dataset.removeClassAttribute ds
            let clu = Cluster.buildClusterer ctype para dsNoLabel
            let eval = new clusterers.ClusterEvaluation()
            eval.setClusterer(clu)
            // if the dataset has the label setted, eval automatically tests with labels
            eval.evaluateClusterer(ds)
            eval
            

    let getClusterSummary (eval:clusterers.ClusterEvaluation) = 
        eval.clusterResultsToString()

    let getAssignments (eval:clusterers.ClusterEvaluation) = 
        eval.getClusterAssignments()
    
